/* eslint-disable @typescript-eslint/indent */
import { Ref, SetupContext, computed, defineComponent, nextTick, ref, watch } from 'vue';
import { Tag, TagsProps, tagsProps } from './tags.props';
import './tags.scss';
import { useDraggable } from './composition/use-draggable';
import { TagsInnerElement } from './composition/types';

export default defineComponent({
    name: 'FTags',
    props: tagsProps,
    emits: ['remove', 'selectionChange', 'change'],
    setup(props: TagsProps, context: SetupContext) {
        const selectable = ref(props.selectable);
        const customClass = ref(props.customClass);
        const innerTags = ref<Tag[]>(props.data);
        const tagType = ref(props.tagType);
        const tagStyle = ref(props.tagStyle);
        const showColor = ref(props.showColor);
        const activeTagValue = ref(props.activeTag);
        const customStyle = ref(props.customStyle);
        const showClose = ref(props.showClose);
        const showAddButton = ref(props.showAddButton);
        const addInput = ref(props.showInput);
        const addButtonText = ref(props.addButtonText);
        const enableAddButton = ref(props.enableAddButton);
        const placeholder = ref(props.placeholder);
        const addTagValue = ref('');
        const activeTag = ref<Tag>();
        const tagsContainerRef = ref<any>();
        const wrapText = ref(props.wrapText);
        const inputBoxRef = ref<any>();

        function initInnerElements(): TagsInnerElement[] {
            const innerElements = innerTags.value.map<TagsInnerElement>((tag: Tag) => ({ type: 'Tag', payload: tag }));
            if (showAddButton.value) {
                innerElements.push({ type: 'AddButton' });
            }
            return innerElements;
        }

        const innerElements = ref<TagsInnerElement[]>(initInnerElements());

        function updateTags(latestTags: Tag[]) {
            innerTags.value = latestTags;
            innerElements.value = initInnerElements();
        }

        const inputIndex = computed(() => {
            return innerElements.value.findIndex((elementInTags: TagsInnerElement) => elementInTags.type === 'Input');
        });

        const useDraggableComposition = useDraggable(props, context, innerTags, innerElements);
        const { dragstart, dragenter, dragover, dragend } = useDraggableComposition;

        watch(() => props.data, (latestTags: Tag[]) => updateTags(latestTags));

        const tagsClass = computed(() => {
            const classObject = {
                'farris-tags': true,
                'farris-tags-checkable': selectable.value,
                'farris-tags-nowrap': !wrapText.value
            } as Record<string, boolean>;
            customClass.value.split(' ').reduce((result: Record<string, boolean>, targetClassName: string) => {
                result[targetClassName] = true;
                return result;
            }, classObject);
            return classObject;
        });

        function tagItemClass(tag: Tag) {
            const classObject = {
                'farris-tag-item': true,
                'farris-tag-item-checked': selectable.value && tag.checked,
                'farris-tag-item-checkable': selectable.value,
                'farris-tag-item-has-color': showColor.value,
                'farris-tag-item-actived': activeTagValue.value === tag.name
            } as Record<string, boolean>;
            if (tagType.value) {
                const tagTypeClassName = `farris-tag-item-${tagType.value}`;
                classObject[tagTypeClassName] = true;
            }
            return classObject;
        }

        const tagItemStyle = computed(() => {
            const styleObject = {} as Record<string, any>;
            if (customStyle.value) {
                customStyle.value.split(';').reduce((result: Record<string, any>, styleString: string) => {
                    const styles = styleString.split(':');
                    result[styles[0]] = styles[1];
                    return result;
                }, styleObject);
            }
            return styleObject;
        });

        function onClickTagItem(e: MouseEvent, currentTag: Tag) {
            if (selectable.value && currentTag.selectable) {
                currentTag.checked = !currentTag.checked;
                activeTagValue.value = currentTag.checked ? currentTag.name : '';
                currentTag.checked && innerTags.value.filter((tag: Tag) => tag.name !== currentTag.name)
                    .forEach((otherTag: Tag) => {
                        otherTag.checked = !currentTag.checked;
                    });
                context.emit('change', innerTags.value);
                context.emit('selectionChange', currentTag);
            }
        }

        function onKeyDownTagItem(payload: KeyboardEvent, currentTag: Tag) {
            let leftScroll;
            switch (payload.key) {
                case 'Backspace':
                    innerTags.value = innerTags.value.filter((tag: Tag) => tag.name !== currentTag.name);
                    context.emit('change', innerTags.value);
                    break;
                case 'ArrowLeft':
                    leftScroll = Math.max(tagsContainerRef.value.scrollLeft + 20, 0);
                    tagsContainerRef.value.scrollLeft = leftScroll;
                    break;
                case 'ArrowRight':
                    leftScroll = Math.max(tagsContainerRef.value.scrollLeft - 20, 0);
                    tagsContainerRef.value.scrollLeft = leftScroll;
            }
        }

        function onClickCloseHandler(e: MouseEvent, targetTag: Tag) {
            const targetTagIndex = innerTags.value.findIndex((tag: Tag) => tag.name === targetTag.name);
            if (targetTagIndex > -1) {
                const [removedTag] = innerTags.value.splice(targetTagIndex, 1);
                innerElements.value = initInnerElements();
                context.emit('change', innerTags.value);
                context.emit('remove', innerTags.value, removedTag, targetTagIndex);
            }
            e.stopPropagation();
        }

        const addButtonClass = computed(() => {
            const classObject = {
                'farris-tag-item': true,
                'farris-tag-add-button': true,
                'farris-tag-add-button-disabled': !enableAddButton.value
            } as Record<string, boolean>;
            return classObject;
        });

        function onClickAddHandler(e: MouseEvent) { }

        function renderAddButtonElement() {
            return (
                <li class={addButtonClass.value} onClick={(payload: MouseEvent) => onClickAddHandler(payload)}>
                    <span class="f-icon f-icon-amplification"></span>
                    <span class="farris-tag-add-text">{addButtonText.value}</span>
                </li>
            );
        }

        function removeTagByInput(inputElement: HTMLElement) {
            if (inputElement && !inputElement.innerText) {
                const caretIndex = inputIndex.value;
                if (caretIndex > 0) {
                    const [removedTag] = innerTags.value.splice(caretIndex - 1, 1);
                    innerElements.value.splice(caretIndex - 1, 1);
                    context.emit('remove', innerTags.value, removedTag, caretIndex - 1);
                }
            }
        }

        function insertTagTo(caretIndex: number, name: string, value: string) {
            if (caretIndex > -1) {
                innerTags.value.splice(caretIndex, 0, { name, value, selectable: selectable.value });
                innerElements.value = initInnerElements();
                context.emit('change', innerTags.value);
            }
        }

        function addNewTagByInput(inputElement: HTMLElement) {
            if (inputElement && inputElement.innerText) {
                const newTagText = inputElement.innerText;
                const caretIndex = inputIndex.value;
                insertTagTo(caretIndex, newTagText, newTagText);
                inputElement.innerText = '';
            }
        }

        function onAddInputBlur($event: FocusEvent) {
            const inputElement = $event.target as HTMLElement;
            if (inputElement) {
                inputElement.innerText ? addNewTagByInput(inputElement) : context.emit('change', innerTags.value);;
            }
        }

        function onAddInputKeyup($event: KeyboardEvent) {
            if ($event.key === 'Enter') {
                addNewTagByInput($event.target as HTMLElement);
            }
            if ($event.key === 'Backspace') {
                removeTagByInput($event.target as HTMLElement);
                nextTick(() => {
                    inputBoxRef.value && inputBoxRef.value.focus();
                });
            }
        }

        function renderTagInputElement() {
            return (
                <li class="farris-tag-input-box">
                    <div ref={inputBoxRef} class="form-control" contenteditable="true"
                        onKeyup={onAddInputKeyup} onBlur={onAddInputBlur}></div>
                </li>
            );
        }

        function renderTagElement(tag: Tag, index: number) {
            return (
                <li
                    class={tagItemClass(tag)}
                    style={tagItemStyle.value}
                    tabindex={0}
                    id={tag.name}
                    key={tag.name}
                    onClick={(payload: MouseEvent) => onClickTagItem(payload, tag)}
                    onKeydown={(payload: KeyboardEvent) => onKeyDownTagItem(payload, tag)}
                    draggable="true"
                    onDragstart={(payload: DragEvent) => dragstart(payload, tag, index)}
                    onDragenter={(payload: DragEvent) => dragenter(payload, index)}
                    onDragend={(payload: DragEvent) => dragend(payload, tag)}
                    onDragover={(payload: DragEvent) => dragover(payload, index)}>
                    <div title={tag.name} class="tag-box">{tag.name}</div>
                    {showClose.value && (
                        <span class="tag-delete">
                            <i
                                class="f-icon f-icon-close"
                                onClick={(payload: MouseEvent) => onClickCloseHandler(payload, tag)}></i>
                        </span>
                    )}
                </li>
            );
        }

        function renderInnerElements() {
            return innerElements.value.map((innerElement: TagsInnerElement, index: number) => {
                switch (innerElement.type) {
                    case 'Tag':
                        return renderTagElement(innerElement.payload as Tag, index);
                    case 'AddButton':
                        return renderAddButtonElement();
                    case 'Input':
                        return renderTagInputElement();
                }
            });
        }

        const tagItemContainerClass = computed(() => {
            const classObject = {
                'farris-tags-item-container': true,
                'farris-tag-item-capsule': tagStyle.value === 'capsule'
            } as Record<string, boolean>;
            return classObject;
        });

        function onscroll(payload: WheelEvent) {
            const scrollLeft = Math.max(tagsContainerRef.value.scrollLeft - payload.deltaX, 0);
            tagsContainerRef.value.scrollLeft = scrollLeft;
        }

        function getClickingTagIndex(clientXOnClicking: number) {
            const tagsContainerElement = tagsContainerRef.value as HTMLElement;
            const lastTagIndex = innerTags.value.length - 1;
            if (tagsContainerElement) {
                const clickingTagIndex = Array.from(tagsContainerElement.children)
                    .filter((elementInTagsContaner: Element) => {
                        return elementInTagsContaner.className.indexOf('farris-tag-item') > -1;
                    })
                    .findIndex((tagElement: Element) => {
                        const tagRect = tagElement.getBoundingClientRect();
                        const tagComputedStyle = window.getComputedStyle(tagElement) as any;
                        const occupiedWidth = parseInt(tagComputedStyle['margin-left'], 10) + tagRect.width
                            + parseInt(tagComputedStyle['margin-right'], 10);
                        return (tagRect.left + occupiedWidth) > clientXOnClicking;
                    });
                if (clickingTagIndex > -1) {
                    return clickingTagIndex;
                }
            }
            return lastTagIndex;
        }

        function onClick(payload: MouseEvent) {
            if (addInput.value) {
                const clickingTagIndex = getClickingTagIndex(payload.clientX);
                const indextToInsertInput = clickingTagIndex + 1;
                const innerElementsWithoutInput = innerElements.value
                    .filter((innerElement: TagsInnerElement) => innerElement.type !== 'Input');
                const elementsToShow = innerElementsWithoutInput;
                elementsToShow.splice(indextToInsertInput, 0, { type: 'Input' });
                innerElements.value = elementsToShow;
                nextTick(() => {
                    inputBoxRef.value && inputBoxRef.value.focus();
                });
            }
        }

        function onDrop(payload: DragEvent) {
            payload.preventDefault();
            payload.stopPropagation();
            const transtedData = payload.dataTransfer?.getData('Text');
            if (transtedData) {
                const draggingData = transtedData.split(':');
                const name = draggingData[0];
                const value = draggingData[1];
                const tags = [...innerTags.value];
                const hasAddedSameTag = tags.findIndex((searchElement: Tag) => {
                    console.log(`${searchElement.value} === ${value} is ${searchElement.value === value}`);
                    return searchElement.value === value;
                }) > -1;
                if (!hasAddedSameTag) {
                    insertTagTo(innerTags.value.length, name, value);
                }
            }
        }

        function onDragover(payload: DragEvent) {
            payload.preventDefault();
        }

        return () => {
            return (
                <div class={tagsClass.value} onClick={onClick} onDrop={onDrop} onDragover={onDragover}>
                    <ul ref={tagsContainerRef} class={tagItemContainerClass.value}
                        onWheel={(payload: WheelEvent) => onscroll(payload)}>{renderInnerElements()}</ul>
                </div>
            );
        };
    }
});
